#include "stdafx.h"
#include "Test.h"
#include "Globals.h"
#include <stdio.h>

using namespace std;

IBarrier* globalBar;
AtomicInteger globalCounter;
int g_counter;
MatrixArray* globalMatArray;
MatrixContainer* globalResult;

void Test::setBarrier(IBarrier *barrier)
{
	this->barrier = barrier;

	globalBar = barrier;
}

class CountThread : public Thread
{
public:
	int id;
	CountThread(int id) : Thread()
	{
		this->id = id;
	}

	virtual void run()
	{
		globalBar->initThreadLocals(id);

		int res = 0;
		int countTo = Globals::countTo;
		for (int i = 0; i < countTo ; i++)
		{
			
			//res = globalCounter.get();
			res = g_counter;
			globalBar->await();
			res++;
			g_counter = res;
			//globalCounter.set(res);
			
		}
		
		//cout << globalBar->getBarrierName() << ", thread, " << id << ", counter value, " << res << endl;
		//printf("%s, thread, %d, counter value, %d\n", globalBar->getBarrierName(), id, res); fflush(stdout);
		
	}
};

void CounterTest::performTest()
{

	// Local chache of the number of threads.
	int numOfThreads = Globals::numberOfThreads;
	//globalCounter = counter;
	//
	//globalCounter.set(0);
	
	CountThread** threads = (CountThread**) calloc(numOfThreads, sizeof(CountThread*));
	for (int testIter = 0 ; testIter < Globals::numberOfIterations; testIter++)
	{	
		printf("starting iteration number %d, count to %d, for %d threads\n", testIter, Globals::countTo, Globals::numberOfThreads);
		g_counter = 0;
		for (int i = 0; i < numOfThreads; i++)
		{
			threads[i] = new CountThread(i);
		}

		tick_t startTime = System::currentTimeMillis();
		
		for (int i = 0; i < numOfThreads; i++)
		{
			threads[i]->start();
		}

		for (int j = 0; j < numOfThreads; j++)
		{
			threads[j]->join();

			//		printf("Counter test, %s, Joined thread number, %d, finished after, %u\n",globalBar->getBarrierName(), j, (System::currentTimeMillis() - startTime));
			//		fflush(stdout);
		}
		tick_t finishTime = System::currentTimeMillis();
		printf("%s, %d, threads, finished after, %llu, millis, shared counter value, %d\n", globalBar->getBarrierName(), Globals::numberOfThreads, finishTime - startTime, g_counter);
		fflush(stdout);
		for (int i = 0; i < numOfThreads; i++)
		{
			delete threads[i];
		}
	}
	free(threads);

#ifdef WIN32
	system("pause");
#endif
}

class LocalCountThread : public Thread
{
public:
	int id;
	int _countTo;
	LocalCountThread(int id, int countTo) : Thread()
	{
		this->id = id;
		this->_countTo = countTo;
	}
	virtual void run();

};

void LocalCountThread::run()
{

	int res = 0;
	int localCounter = 0;
	globalBar->initThreadLocals(id);
	for (int i = 0; i < _countTo ; i++)
	{
		res = localCounter;
		globalBar->await();
		res++;
		localCounter = res;

	}
	//cout << globalBar->getBarrierName() << ", thread, " << id << ", counter value, " << res << endl;
	//printf("%s, thread, %d, local counter value, %d\n", globalBar->getBarrierName(), id, res);
	//fflush(stdout);

}

void LocalCounterTest::performTest()
{
	// Local chache of the number of threads.
	int numOfThreads = Globals::numberOfThreads;
	LocalCountThread** threads = (LocalCountThread**) calloc(numOfThreads, sizeof(LocalCountThread*));

	for (int testIter = 0 ; testIter < Globals::numberOfIterations; testIter++)
	{
		counter = 0;
		for (int i = 0; i < numOfThreads; i++)
		{
			threads[i] = new LocalCountThread(i, Globals::countTo);
		}

		tick_t startTime = System::currentTimeMillis();
		printf("starting iteration number %d, count to %d, for %d threads\n", testIter, Globals::countTo, Globals::numberOfThreads);
		fflush(stdout);

		for (int i = 0; i < numOfThreads; i++)
		{
			threads[i]->start();
		}

		for (int j = 0; j < numOfThreads; j++)
		{
			threads[j]->join();

			printf("LocalCounter test, %s, Joined thread number, %d, finished after, %u millies\n",globalBar->getBarrierName(), j, (System::currentTimeMillis() - startTime));
			fflush(stdout);
		}

		tick_t finishTime = System::currentTimeMillis();
		printf("%s, %d, threads finished after, %llu, millis, local counter value, %d\n", globalBar->getBarrierName(), Globals::numberOfThreads, finishTime - startTime, g_counter);
		fflush(stdout);

		for (int i = 0; i < numOfThreads; i++)
		{
			delete threads[i];
		}
	}
	free(threads);
	
#ifdef WIN32
	system("pause");
#endif
}

class MatrixThread : public Thread
{
	int id;
public:
	void* result;

	MatrixThread(int id) : Thread()
	{
		this->id = id;
	}
	virtual void run()
	{
		int order = floor(sqrt(double(Globals::numberOfThreads)));
		globalBar->initThreadLocals(id);

		int col = id / order;
		int row = id % order;

		double value;
		MatrixContainer* matrix;

		for (int i = 0; i < globalMatArray->getNumOfMatrices(); i++)
		{
			value = 0;
			matrix = globalMatArray->getMatrix(i);

			for (int j = 0; j < order; j++)
			{
				value += globalResult->getValue(row, j) * matrix->getValue(j, col);
			}

			globalBar->await();

			globalResult->setValue(row, col, value);

			globalBar->await();
		}

//		printf("thread %d done.\n", id); fflush(stdout);
		id = 0;
		result = (void*) globalResult;
	}
};

MatrixTest::MatrixTest()
{
	MatrixLoader* loader = new MatrixLoader();
	this->matArray = loader->loadMatrices(MATRIX_FILE_NAME);

	int order = floor(sqrt(double(Globals::numberOfThreads)));

	globalMatArray = this->matArray;
	globalResult = new MatrixContainer(order);

	//set identity matrix
	for (int i = 0; i < order; i++)	globalResult->setValue(i, i, 1);
}

void MatrixTest::performTest()
{
	int numOfThreads = Globals::numberOfThreads;
	MatrixThread** threads = (MatrixThread**) calloc(numOfThreads, sizeof(MatrixThread*));

	for (int testIter = 0 ; testIter < Globals::numberOfIterations; testIter++)
	{
		printf("starting iteration number %d, matrix, , for, %d, threads\n", testIter, Globals::numberOfThreads);
		tick_t startTime = System::currentTimeMillis();

		for (int i=0; i < numOfThreads; i++)
		{
			threads[i] = new MatrixThread(i);
		}

		for (int i=0; i < numOfThreads; i++)
		{
			threads[i]->start();	
		}

		for (int i=0; i < numOfThreads; i++)
		{
			threads[i]->join();
		}
		tick_t finishTime = System::currentTimeMillis();
		printf("%s, %d, threads, finished after, %llu, millis, matrix test, -1\n", globalBar->getBarrierName(), Globals::numberOfThreads, finishTime - startTime);
		fflush(stdout);
		for (int i=0; i < numOfThreads; i++)
		{
			delete threads[i];
		}

	}
	free(threads);

#ifdef WIN32
	system("pause");
#endif
}

Test* TestFactory::createTest(IBarrier* barrier, TestType testType)
{
	Test* result = NULL;

	switch (testType)
	{
		case Counter: result = new CounterTest(); break;
		case LocalCounter: result = new LocalCounterTest(); break;
		case Matrix: result = new MatrixTest(); break;
	}

	if (result != NULL)
	{
		result->setBarrier(barrier);
	}

	return result;
}

MatrixContainer::MatrixContainer(int order)
{
	this->order = order;
	matrix = (double*) calloc(order * order, sizeof(double));
}

int MatrixContainer::getOrder()
{
	return order;
}

double MatrixContainer::getValue(int row, int col)
{
	return matrix[row + order*col];
}

void MatrixContainer::setValue(int row, int col, double value)
{
	matrix[row + order*col] = value;
}

int MatrixArray::getNumOfMatrices()
{
	return numOfMatrices;
}

MatrixArray::MatrixArray(int numOfMatrices)
{
	this->numOfMatrices = numOfMatrices;
	this->matrices = (MatrixContainer**) calloc(numOfMatrices, sizeof(MatrixContainer*));
}

void MatrixArray::setMatrix(int index, MatrixContainer* matrix)
{
	matrices[index] = matrix;
}

MatrixContainer* MatrixArray::getMatrix(int index)
{
	return matrices[index];
}

MatrixArray* MatrixLoader::loadMatrices(char* filename)
{
	FILE* inputFile = fopen("matrix3.txt", "r");

	int order;
	int numOfMatrices;
	double value;

	char temp[256];

	fscanf(inputFile, "ORDER = %d\n", &order);
	fscanf(inputFile, "NUM_OF_MATRICES = %d\n", &numOfMatrices);

	MatrixArray* result = new MatrixArray(numOfMatrices);

	for (int i = 0; i < numOfMatrices; i++)
	{
		MatrixContainer* matrix = new MatrixContainer(order);
		
		fscanf(inputFile, "%s =", temp);

		for (int j = 0; j < order; j++)
		{
			for (int k = 0; k < order; k++)
			{
				if ((k == order) && (j == order))
				{
					fscanf(inputFile, " %lf", &value);
				}
				else
				{
					fscanf(inputFile, " %lf,", &value);
				}
				
				matrix->setValue(j, k, value);
			}
		}

		result->setMatrix(i, matrix);
	}

	fclose(inputFile);

	return result;
}
